import org.hibernate.*;
import org.apache.commons.lang.StringUtils;
import java.text.SimpleDateFormat;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.zip.*;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.codec.EncoderException;

abstract class RealtyOfferObject implements ObjectIF {
    RealtyObject realtyObject = null;
    FlatDetails flatDetails = null;
    HouseDetails houseDetails = null;
    Price priceObject = null;
    List<String> externalIdList = new ArrayList<String>();
    Integer realtyTypeId = 0;
    String realtyType = "";

    String siteDefinedOffersNum = "";

    public SessionFactory sessionFactory;
    protected SiteMeshFetcher siteFetcher = null;
    private StringBuilder reusableOffersContent;
    private SimpleDateFormat fileNameFormat = new SimpleDateFormat("yyyy-MM-dd-HH");
    private Pattern offersNumberFetchpattern = Pattern.compile("<div[^<]*\\s*<b[^<]*<span[^<]*>\\s*(\\d*)\\s*</span>.*?>",
            Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.UNICODE_CASE);

    private Pattern offersFetchPattern = Pattern.compile("<th[^>]*>\\s*<a[^<]*[\\w\\s\\W]*?</th>\\s*<td[^>]*>\\s*(?:<b>\\w*\\W*\\s*<span[^>]+>)?(.*?)(?:</span></b>)?\\s*</td>",
            Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.UNICODE_CASE);
    private Matcher matcher = null;

    RealtyOfferObject() {
        siteFetcher = new SiteMeshFetcher();
        reusableOffersContent = new StringBuilder();
        //        realtyObject = new RealtyObject();
    }
    public abstract Double fillDetails(String[] details, Date date);
    public abstract List<RealtyObject> findRealtyByExternalId(Long externalId);
    public abstract List<RealtyObject> findExcludedRealty(String csvStr);

    public String fetchSiteContent(String objectType, String viewType) {
        String siteContent = null;
        if (siteFetcher != null)
            siteContent = siteFetcher.fetchSiteContent(objectType, viewType);
        return siteContent;
    };
    protected void storeSiteContent(String arcivePath, String content, Date date) {
        String path = arcivePath + File.separator + realtyType + File.separator;
        //        Calendar cal = new GregorianCalendar();
        //        cal.setTime(date);
        File file = new File(path + "" + fileNameFormat.format(date) + ".gz");
        println("Check path:" + path + "" + fileNameFormat.format(date) + ".gz");
        if (file.exists()) {
            println("File already exist.");
            return;
        } else {
            println("Creating new file.");
            file.createNewFile();
        }
        OutputStream oFile = null;
        GZIPOutputStream gzon = null;
        OutputStreamWriter xover = null;
        BufferedWriter os = null;
        BufferedReader is = null;
        try {
            oFile = new FileOutputStream(file);
            gzon = new GZIPOutputStream(oFile);

            xover = new OutputStreamWriter(gzon);
            os = new BufferedWriter(xover);
            is = new BufferedReader(new StringReader(content));
            //char[] buff = new char[1024];
            String line = "";

            //String offersContent = "";
            println("Store content... ");
            while ((line = is.readLine()) != null) {
                os.writeLine(line)
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //            if (oFile != null) {
            //               oFile.close();
            //           }
            //           if (gzon != null) {
            //                gzon.close();
            //            }
            //            if (xover != null) {
            //                xover.flush();
            //                xover.close();
            //            }
            if (os != null) {
                os.flush();
                os.close();
            }
            if (is != null)
                is.close();
            oFile = null;
            gzon = null;
            xover = null;
            os = null;
            is = null;
        }
    };
    protected List<File> getFileList(String archivePath) {
        List<File> fileList = new ArrayList<File>();
        if (StringUtils.isEmpty(archivePath)) {
            archivePath = "F:\\SPOOL\\data";
            println("Path to archive files is empty, defaultPath = " + archivePath);
        }
        File dir = new File(archivePath);
        try {
            if (dir.exists()) {
                println("Dir Exist!");
                if (dir.isDirectory()) {
                    println("Dir:" + dir.getName());
                    dir.eachFile {
                        if (it.isFile())
                            fileList.add(it);
                    }
                } else {
                    if (dir.isFile())
                        fileList.add(dir);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fileList;
    }
    public String fetchFileSiteContent(File file) {
        def fileName = file.name;
        if (fileName.endsWith(".gz")) {
            reusableOffersContent.replace(0, reusableOffersContent.length(), "");
            println("-----------------------------------------------------------------------------");
            Runtime rt = Runtime.getRuntime();
            long total = rt.totalMemory();
            long free = rt.freeMemory();
            println("freeMem = >" + free + "  total = >" + total);
            if (free < 25000000) {
                rt.gc();
                int count = 0;
                while (free < 20000000) {
                    free = rt.freeMemory();
                    total = rt.totalMemory();

                    Runtime.getRuntime().runFinalization();
                    System.gc();
                    Runtime.getRuntime().gc();
                    println("#" + count++ + " - " + "freeMem = >" + free + "  total = >" + total);

                }
            }

            total = rt.totalMemory();
            free = rt.freeMemory();
            println("freeMem = >" + free + "  total = >" + total);
            println("-----------------------------------------------------------------------------");


            InputStream iFile = null;
            GZIPInputStream gzin = null;
            InputStreamReader xover = null;
            BufferedReader is = null;
            try {
                iFile = new FileInputStream(file);
                gzin = new GZIPInputStream(iFile);
                xover = new InputStreamReader(gzin);
                is = new BufferedReader(xover);
                //char[] buff = new char[1024];
                String line = "";

                //String offersContent = "";
                println("loading... ");
                while ((line = is.readLine()) != null) {
                    reusableOffersContent.append(line);
                }

                println("loading completed ! ");
                Date parseDate = fileNameFormat.parse(fileName.substring(0, (fileName.indexOf(".")) == -1 ? fileName.length() : fileName.indexOf(".")));
                //                realtyObject.date = parseDate;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            } finally {
                if (iFile != null) {
                    iFile.close();
                }
                if (gzin != null) {
                    gzin.close();
                }
                if (xover != null) {
                    xover.close();
                }
                if (is != null) {
                    is.close();
                }
                iFile = null;
                gzin = null;
                xover = null;
                is = null;

            }
        }
        return reusableOffersContent.toString();
    };
    public List<String[]> parseHtmlContent(String content) {
        String[] matchedGroups = null;
        List<String[]> matchedGroupsList = new ArrayList<String[]>();
        try {
            matcher = offersNumberFetchpattern.matcher(content);
            if (matcher.find()) {
                siteDefinedOffersNum = matcher.group(1);
                println("offers numbers match successfull!!! " + siteDefinedOffersNum);
            }
            println("offers numbers matching completed!");
            //        realtyObject.data = offersNum;
            //        realtyObject.add_date = date;
            //        println("Add date is:" + date);
            println("offers data matching  ...");
            matcher = offersFetchPattern.matcher(content);
            Boolean matcherFind = true;
            int offerCounter = 0;
            while (matcherFind) {
                int i = 0;
                matchedGroups = new String[15];
                while (i < 15 && matcherFind) {
                    if ((matcherFind = matcher.find())) {
                        matchedGroups[i] = matcher.group(1);
                        //println("i="+i+" matcherFind="+matcherFind+" groups[i]="+matchedGroups[i]);
                        i++;
                    }
                }
                if (!matcherFind && i == 0) continue;
                matchedGroupsList.add(matchedGroups);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return matchedGroupsList;
    };
    public void processRenewedSiteData(String arcivePath) {
        boolean tryFetchData = true;
        while (tryFetchData) {
            externalIdList = new ArrayList<String>();
            String content = fetchSiteContent(realtyType, Constants.VIEW_TYPE_BLOCK);
            if (StringUtils.isEmpty(content)) {
                //new throw siteFetcherExeption;
                println("Error while fetching content from site.");
                return;
            }
            Date grabDate = new Date();
            storeSiteContent(arcivePath, content, grabDate);
            List<String[]> parsedData = parseHtmlContent(content);
            println("Found " + parsedData.size() + " items.");
            if (parsedData.size() != Integer.parseInt(siteDefinedOffersNum).intValue()) {
                continue;
            }
            trySave(parsedData, grabDate);
            fixOffersActivity(externalIdList, grabDate);
            tryFetchData = false;
        }
    }
    public void processArchivedSiteData(String path, SessionFactory sessionFactory) {
        if (this.sessionFactory == null) this.sessionFactory = sessionFactory;

        List<File> fileList = getFileList(path + File.separator + realtyType);
        println("Grabbed files count: " + fileList.size());
        Iterator fileListIter = fileList.iterator();
        while (fileListIter.hasNext()) {
            externalIdList = new ArrayList<String>();
            File file = (File) fileListIter.next();
            String content = fetchFileSiteContent(file);
            String fileName = file.getName();
            if (StringUtils.isEmpty(content)) {
                //new throw siteFetcherExeption;
                println("Error while fetching content from file:" + fileName);
                continue;
            }
            println("Try parse " + fileName + " file.");
            List<String[]> parsedData = parseHtmlContent(content);
            println("Found " + parsedData.size() + " items.");

            Date grabDate = fileNameFormat.parse(fileName.substring(0, (fileName.indexOf(".")) == -1 ? fileName.length() : fileName.indexOf(".")));
            if (sessionFactory != null) {
                def session = sessionFactory.getCurrentSession();
                session.getTransaction().begin();
                trySave(parsedData, grabDate);
                fixOffersActivity(externalIdList, grabDate);
                //SessionFactoryUtils.getSession(sessionFactory, false);

                session.getTransaction().commit();
                session.setFlushMode(FlushMode.ALWAYS);
                session.setCacheMode(CacheMode.IGNORE);
                session.flush();
                session.clear();
                println("*** Hibernate session flush/clear ***");
            }
            if (realtyObject != null) {
                realtyObject.finalize();
                realtyObject.flatDetails = null;
                realtyObject.houseDetails = null;
                realtyObject = null;
            }
            if (flatDetails != null) {
                flatDetails.finalize();
                flatDetails = null;
            }
            if (houseDetails != null) {
                houseDetails.finalize();
                houseDetails = null;
            }
            if (priceObject != null) {
                priceObject.finalize();
                priceObject = null;
            }

        }
    }

    public void trySave(List<String[]> parsedData, Date date) {
        String hqlStr = "";
        parsedData.each {
            Double price = fillDetails(it, date);
            //            println("try to save: " + realtyObject.externalId);
            if (realtyObject.externalId != null && realtyObject.externalId > 0) {
                List<RealtyObject> objectList = findRealtyByExternalId(realtyObject.externalId);
                //println("objectList:" + objectList.size());
                if (objectList != null && objectList.size() >= 0) {
                    if (objectList.size() > 1) {
                        println("RealtyObject duplicates are found with Id: ");
                        objectList.each {
                            print(it.id); print("-");
                        }
                        println();
                    }
                    if (objectList.size() > 0) {
                        RealtyObject object = (RealtyObject) objectList.get(0);
                        List priceList = Price.findAll("from Price p where p.realtyObject.id = ? order by p.date desc", [object.id]);
                        //println(priceList.get(priceList.size() - 1).priceValue + "--" + price);
                        Price lastPriceObject = (Price) priceList.get(0);
                        Price preLastPriceObject = null;
                        if (priceList.size() > 1)
                            preLastPriceObject = (Price) priceList.get(1);
                        if (!isDateExist(priceList, priceObject.date)) {//Math.abs(lastPriceObject.priceValue - price) > 1.0 &&
                            switch (lastPriceObject.state) {
                                case Constants.NEW:
                                    priceObject.state = Constants.ORDINARY;
                                    break;

                                case Constants.REMOVED:
                                    priceObject.state = Constants.NEW;
                                    break;
                                default:
                                    priceObject.state = Constants.ORDINARY;
                            }
                            if (Math.abs(lastPriceObject.priceValue - price) > 1.0 ||
                                    (preLastPriceObject != null && Math.abs(preLastPriceObject.priceValue - price) > 1.0) ||
                                    lastPriceObject.state == Constants.REMOVED || lastPriceObject.state == Constants.NEW) {
                                print("extId:" + object.externalId + "==>Price/State changed. Saving...");
                                print((preLastPriceObject != null ? preLastPriceObject.priceValue : "null") + "-->");
                                print(lastPriceObject.priceValue + "-->" + price + " ");
                                println(lastPriceObject.state + "-->" + priceObject.state);
                                object.addToPrice(priceObject);
                                if (flatDetails != null)
                                    object.setFlatDetails(flatDetails);
                                if (houseDetails != null)
                                    object.setHouseDetails(houseDetails);

                                if (!object.save(flush:true))
                                    println(realtyObject.errors.allErrors)
                            } else {
                                lastPriceObject.date = priceObject.date;
                                if (!lastPriceObject.save(flush:true))
                                    println(realtyObject.errors.allErrors);
                            }

                        } /*else {
                                print("==> Price not changed ... leave old value ...");
                            }   */

                    } else {
                        println("try to save: " + realtyObject.externalId);
                        priceObject.state = Constants.NEW;
                        if (!realtyObject.save(flush:true))
                            println(realtyObject.errors.allErrors)

                    }

                }
            }
        }
    }
    /**
    *  Mark
    *    removed offers as excluded
    */
    public void fixOffersActivity(List<String> externalIdList, Date date) {
        println("Mark removed offers");
        if (externalIdList.size() <= 0)
            return;
        String csvStr = StringUtils.join(externalIdList.toArray(), ",");
        //        println("--"+csvStr+"--");
        List<RealtyObject> excludedRealty = findExcludedRealty(csvStr);
        println("Removed " + excludedRealty.size() + " realty objects.");
        excludedRealty.each {
            //            println("2");
            List priceStateList = Price.findAll("from Price p where p.realtyObject.id = ? order by p.date desc", [((RealtyObject) it).id]);
            //println("try externalId=" + it.externalId + ". Size=" + (priceStateList != null ? priceStateList.size() : "null"));
            if (priceStateList != null && priceStateList.size() > 0) {
                Price lastPriceState = (Price) priceStateList.get(0);
                //                println("curDate=" + date + ". lastDate=" + lastPriceState.date + ". State=" + lastPriceState.state);
                if (date > lastPriceState.date) {
                    if (lastPriceState.state == Constants.ORDINARY) {
                        lastPriceState.state = Constants.REMOVED;
                        lastPriceState.date = date;
                        lastPriceState.save();
                    } else
                        if (lastPriceState.state == Constants.NEW) {
                            Price newPriceState = new Price();
                            newPriceState.date = date;
                            newPriceState.state = Constants.REMOVED;
                            newPriceState.priceValue = lastPriceState.priceValue;
                            it.addToPrice(newPriceState);
                            it.save();
                        }
                }
                //println("5");
            }
        }
    }
    public boolean isDateExist(List priceList, Date date) {
        int i = 0;
        boolean exist = false;
        priceList.each {
            java.util.Calendar cal1 = java.util.Calendar.getInstance();
            java.util.Calendar cal2 = java.util.Calendar.getInstance();
            cal1.setTime(date);
            cal2.setTime(it.date);
            //            println("#"+i+"-date1:"+date);
            //            println("#"+i+"-date2:"+it.date);

            if ((cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)) &&
                    (cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH)) &&
                    (cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH)) &&
                    (cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR))) {
                //                    println("date1:"+cal1.get(Calendar.YEAR)+"-"+cal1.get(Calendar.MONTH)+"-"+cal1.get(Calendar.DAY_OF_MONTH)+"-"+cal1.get(Calendar.HOUR));
                //                    println("date2:"+cal2.get(Calendar.YEAR)+"-"+cal2.get(Calendar.MONTH)+"-"+cal2.get(Calendar.DAY_OF_MONTH)+"-"+cal2.get(Calendar.HOUR));
                exist = true;
            }
            i++;
        }
        return exist;
    };

}